// EnumFlagSelector.cs
//
// Copyright (c) 2007 Mike Strobel
//
// This source code is subject to the terms of the Microsoft Reciprocal License (Ms-RL).
// For details, see <http://www.opensource.org/licenses/ms-rl.html>.
//
// All other rights reserved.

using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;

using Supremacy.Client;
using Supremacy.Types;

using Expression = System.Linq.Expressions.Expression;

namespace Supremacy.Editor
{
    public class EnumFlagSelector : ListBox
    {
        #region Constants
        public static readonly DependencyProperty EnumTypeProperty;
        public static readonly DependencyProperty ValueProperty;
        #endregion

        #region Fields
        private static Delegate s_bitwiseAnd;
        private static Delegate s_bitwiseOr;
        private static Delegate s_bitwiseNot;

        private readonly StateScope _updateScope;
        #endregion

        #region Constructors
        static EnumFlagSelector()
        {
            ValueProperty = DependencyProperty.Register(
                "Value",
                typeof(object),
                typeof(EnumFlagSelector),
                new FrameworkPropertyMetadata(
                    null,
                    ValueChanged));

            EnumTypeProperty = DependencyProperty.Register(
                "EnumType",
                typeof(Type),
                typeof(EnumFlagSelector),
                new FrameworkPropertyMetadata(
                    null,
                    FrameworkPropertyMetadataOptions.Inherits,
                    EnumTypeChanged));

            ItemsSourceProperty.OverrideMetadata(
                typeof(EnumFlagSelector),
                new FrameworkPropertyMetadata(
                    null,
                    null,
                    CoerceItemsSource));

            SelectionModeProperty.OverrideMetadata(
                typeof(EnumFlagSelector),
                new FrameworkPropertyMetadata(
                    SelectionMode.Multiple,
                    null,
                    CoerceSelectionMode));
        }

        private static object CoerceSelectionMode(DependencyObject d, object baseValue)
        {
            var selector = d as EnumFlagSelector;
            if (selector == null)
                return baseValue;
            return SelectionMode.Multiple;
        }

        private static object CoerceItemsSource(DependencyObject d, object baseValue)
        {
            var selector = d as EnumFlagSelector;
            if (selector == null)
                return baseValue;
            if (selector.EnumType == null)
                return null;
            return Enum.GetValues(selector.EnumType);
        }

        protected override DependencyObject GetContainerForItemOverride()
        {
            return new EnumFlagSelectorItem();
        }

        protected override void ClearContainerForItemOverride(DependencyObject element, object item)
        {
            var container = element as EnumFlagSelectorItem;
            if (container != null)
                BindingOperations.ClearAllBindings(container);
            base.ClearContainerForItemOverride(element, item);
        }

        public EnumFlagSelector()
        {
            _updateScope = new StateScope();

            SetValue(ScrollViewer.HorizontalScrollBarVisibilityProperty, ScrollBarVisibility.Disabled);
            SetValue(ScrollViewer.VerticalScrollBarVisibilityProperty, ScrollBarVisibility.Auto);
        }
        #endregion

        #region Properties and Indexers
        public Type EnumType
        {
            get { return GetValue(EnumTypeProperty) as Type; }
            set { SetValue(EnumTypeProperty, value); }
        }

        public object Value
        {
            get { return GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        public bool IsValueSet
        {
            get { return (GetValue(ValueProperty) != DependencyProperty.UnsetValue); }
        }
        #endregion

        #region Methods
        private static void EnumTypeChanged(
            DependencyObject source,
            DependencyPropertyChangedEventArgs e)
        {
            var selector = source as EnumFlagSelector;
            if (selector == null)
                return;
            selector.InvalidateProperty(ItemsSourceProperty);
            if (selector.IsValueSet)
                selector.UpdateSelection();
        }

        protected object PerformBitwiseAnd(object left, object right)
        {
            if (s_bitwiseAnd == null)
            {
                var parameterLeft = Expression.Parameter(EnumType, "left");
                var parameterRight = Expression.Parameter(EnumType, "right");
                s_bitwiseAnd = Expression.Lambda(
                    Expression.Convert(
                        Expression.And(
                            Expression.Convert(parameterLeft, typeof(UInt64)),
                            Expression.Convert(parameterRight, typeof(UInt64))),
                        this.EnumType),
                    parameterLeft,
                    parameterRight).Compile();
            }
            return s_bitwiseAnd.DynamicInvoke(left, right);
        }

        protected object PerformBitwiseNot(object value)
        {
            if (s_bitwiseNot == null)
            {
                var parameter = Expression.Parameter(EnumType, "value");
                s_bitwiseNot = Expression.Lambda(
                    Expression.Convert(
                        Expression.Not(
                            Expression.Convert(parameter, typeof(UInt64))),
                        this.EnumType),
                    parameter).Compile();
            }
            return s_bitwiseNot.DynamicInvoke(value);
        }

        protected object PerformBitwiseOr(object left, object right)
        {
            if (s_bitwiseOr == null)
            {
                var parameterLeft = Expression.Parameter(EnumType, "left");
                var parameterRight = Expression.Parameter(EnumType, "right");
                s_bitwiseOr = Expression.Lambda(
                    Expression.Convert(
                        Expression.Or(
                            Expression.Convert(parameterLeft, typeof(UInt64)),
                            Expression.Convert(parameterRight, typeof(UInt64))),
                        this.EnumType),
                    parameterLeft,
                    parameterRight).Compile();
            }
            return s_bitwiseOr.DynamicInvoke(left, right);
        }

        protected bool IsValueSelected(object value)
        {
            if (!IsValueSet || (EnumType == null))
                return false;
            return Equals(PerformBitwiseAnd(this.Value, value), value);
        }

        protected override void OnSelectionChanged(SelectionChangedEventArgs e)
        {
            base.OnSelectionChanged(e);

            if (_updateScope.IsWithin)
                return;

            var tempValue = this.Value;

            foreach (var addedItem in e.AddedItems)
                tempValue = PerformBitwiseOr(tempValue, addedItem);
            foreach (var removedItem in e.RemovedItems)
                tempValue = PerformBitwiseAnd(tempValue, PerformBitwiseNot(removedItem));

            this.Value = tempValue;
        }

        private void UpdateSelection()
        {
            if (_updateScope.IsWithin || !IsValueSet || (EnumType == null) || !EnumType.IsEnum)
                return;

            using (_updateScope.Enter())
            {
                SetSelectedItems(
                    Enum.GetValues(this.EnumType)
                    .Cast<object>()
                    .Where(IsValueSelected));
            }
        }

        private static void ValueChanged(
            DependencyObject source,
            DependencyPropertyChangedEventArgs e)
        {
            var selector = source as EnumFlagSelector;
            if (selector == null)
                return;
            if (selector.EnumType != null)
                selector.UpdateSelection();
        }
        #endregion
    }

    internal class EnumFlagSelectorItem : CheckBox
    {
        public static readonly DependencyProperty IsSelectedProperty;

        private static readonly EnumStringConverter s_enumStringConverter;

        static EnumFlagSelectorItem()
        {
            s_enumStringConverter = new EnumStringConverter();

            IsSelectedProperty = Selector.IsSelectedProperty.AddOwner(
                typeof(EnumFlagSelectorItem),
                new FrameworkPropertyMetadata(
                    false,
                    IsSelectedChanged));
        }

        public EnumFlagSelectorItem()
        {
            SetBinding(
                ContentProperty,
                new Binding
                {
                    Converter = s_enumStringConverter
                });
            SetBinding(
                IsCheckedProperty,
                new Binding
                {
                    Source = this,
                    Path = new PropertyPath(IsSelectedProperty),
                    Mode = BindingMode.TwoWay
                });
        }

        public bool IsSelected
        {
            get { return (bool)GetValue(IsSelectedProperty); }
            set { SetValue(IsSelectedProperty, value); }
        }

        private static void IsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var item = d as EnumFlagSelectorItem;
            if (item == null)
                return;
            if (item.IsSelected)
                item.RaiseEvent(new RoutedEventArgs(Selector.SelectedEvent));
            else
                item.RaiseEvent(new RoutedEventArgs(Selector.UnselectedEvent));
        }
    }
}